package com.laopan.wxpay.request.certificates;

import com.laopan.wxpay.WxConstants;
import com.laopan.wxpay.bean.certificate.EncryptCertificate;
import com.laopan.wxpay.bean.certificate.EncryptCertificateInfo;
import com.laopan.wxpay.bean.certificate.EncryptCertificateResult;
import com.laopan.wxpay.bean.certificate.MerchantPrivateKey;
import com.laopan.wxpay.utils.JsonUtils;
import com.laopan.wxpay.utils.PemUtils;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 本地缓存文件
 *
 * @author laopan
 * @className MemoryCacheCertificatesManager
 * @date 2021/2/23 17:17
 */
public class MemoryCacheCertificatesManager implements WxCertificatesManager {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * 用map来存储密钥。
     */
    private final static Map<String, X509Certificate> USER_CERT_MAP = new ConcurrentHashMap<>();
    private final static Map<String, String> SERIAL_NO = new ConcurrentHashMap<>();

    public final MerchantPrivateKey merchantPrivateKey;

    @Override
    public X509Certificate getValidCertificate(String mchId) {
        X509Certificate certificate = USER_CERT_MAP.get(mchId);
        if (certificate == null) {
            return updateAndGet(mchId);
        }
        return certificate;
    }

    public MemoryCacheCertificatesManager(MerchantPrivateKey merchantPrivateKey) {
        this.merchantPrivateKey = merchantPrivateKey;
    }

    private X509Certificate updateAndGet(String mchId) {
        String mchLock = WxConstants.LOCK_CERTIFICATES + mchId;
        synchronized (mchLock.intern()) {
            X509Certificate certificate = USER_CERT_MAP.get(mchId);
            if (certificate == null) {
                certificate = updateCertificates(mchId);
                if (certificate != null) {
                    USER_CERT_MAP.put(mchId, certificate);
                }
            }
            return certificate;
        }
    }

    private X509Certificate deserializeToCerts(String merId, byte[] apiV3Key, EncryptCertificateResult certificateResult)
            throws GeneralSecurityException, IOException {
        AesUtil decryptor = new AesUtil(apiV3Key);
        List<EncryptCertificateInfo> encryptCertificateInfo = certificateResult.getData();
        X509Certificate newCert;
        if (encryptCertificateInfo != null && !encryptCertificateInfo.isEmpty()) {
            for (EncryptCertificateInfo certificateInfo : encryptCertificateInfo) {
                X509Certificate x509Cert;
                try {
                    EncryptCertificate encryptCertificate = certificateInfo.getEncryptCertificate();
                    //解密
                    String cert = decryptor.decryptToString(
                            encryptCertificate.getAssociatedData().replaceAll("\"", "")
                                    .getBytes(StandardCharsets.UTF_8),
                            encryptCertificate.getNonce().replaceAll("\"", "")
                                    .getBytes(StandardCharsets.UTF_8),
                            encryptCertificate.getCipherText().replaceAll("\"", ""));
                    x509Cert = PemUtils.loadCertificate(
                            new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))
                    );
                    registerSerialNo(merId, certificateInfo.getSerialNo());
                    return x509Cert;
                } catch (CertificateExpiredException | CertificateNotYetValidException e) {
                    log.error("解析证书出现异常：", e);
                }
            }
        }
        return null;
    }

    private void registerSerialNo(String merId, String serialNo) {
        SERIAL_NO.put(merId, serialNo);
    }

    /**
     * 更新证书
     *
     * @param mchId
     */
    private X509Certificate updateCertificates(String mchId) {
        PrivateKey privateKey = merchantPrivateKey.getPrivateKey();
        String mchSerialNo = merchantPrivateKey.getSerialNo();
        byte[] apiV3key = merchantPrivateKey.getApiV3key().getBytes(StandardCharsets.UTF_8);
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, privateKey)),
                apiV3key);
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        CloseableHttpResponse response = null;
        X509Certificate newCert = null;
        try {
            HttpGet httpGet = new HttpGet(WxConstants.PAY_HOST_URL + WxConstants.PATH_CERTIFICATES);
            httpGet.addHeader("Accept", "application/json");
            response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            String body = EntityUtils.toString(response.getEntity());
            if (statusCode == HttpStatus.SC_OK) {
                EncryptCertificateResult certificateResult = JsonUtils.fromJson(body, EncryptCertificateResult.class);
                return deserializeToCerts(mchId, apiV3key, certificateResult);

            } else {
                log.warn("Auto update cert failed, statusCode = " + statusCode + ",body = " + body);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public String getUseSerialNo(String mchId) {
        return SERIAL_NO.get(mchId);
    }

    @Override
    public X509Certificate refresh(String mchId) {
        String mchLock = WxConstants.LOCK_CERTIFICATES + mchId;
        synchronized (mchLock.intern()) {
            X509Certificate certificate = updateCertificates(mchId);
            if (certificate != null) {
                USER_CERT_MAP.put(mchId, certificate);
            }
            return certificate;
        }
    }
}
